package top.z.template.parse.ast.expression;

import top.z.template.Context;
import top.z.template.Error;
import top.z.template.Template;
import top.z.template.interpreter.Reflection;
import top.z.template.parse.Ast.Expression;
import top.z.template.parse.Span;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.util.Map;

/**
 * 成员访问(字段, 方法或Map中的实体), 成员只能被读取
 */
public class MemberAccess extends Expression {

    private final Expression object;
    private final Span       name;
    private       Object     cacheMember;

    public MemberAccess(Expression object, Span name) {
        super(name);
        this.object = object;
        this.name = name;
    }

    public Expression getObject() {
        return object;
    }

    public Span getName() {
        return name;
    }

    public Object getCacheMember() {
        return cacheMember;
    }

    public void setCacheMember(Object cacheMember) {
        this.cacheMember = cacheMember;
    }

    @Override
    public Object evaluate(Template template, Context context, OutputStream out) throws IOException {
        Object object = getObject().evaluate(template, context, out);
        if (object == null) {
            throw Error.error("Couldn't find object in context.", getSpan());
        }

        // special case for array.length
        if (object.getClass().isArray() && getName().getText().equals("length")) {
            return Array.getLength(object);
        }

        // special case for map, allows to do map.key instead of map[key]
        if (object instanceof Map) {
            Map<?, ?> map = (Map<?, ?>) object;
            return map.get(getName().getText());
        }

        Object field = getCacheMember();
        if (field != null) {
            try {
                return Reflection.getInstance().getFieldValue(object, field);
            } catch (Throwable t) {
                // fall through
            }
        }

        field = Reflection.getInstance().getField(object, getName().getText());
        if (field == null) {
            throw Error.error("Couldn't find field '" + getName().getText() + "' for object of type '" + object.getClass().getSimpleName() + "'.", getSpan());
        }
        setCacheMember(field);
        return Reflection.getInstance().getFieldValue(object, field);
    }
}
